package solver;

import java.awt.RadialGradientPaint;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import util.Chronograph;
import model.EnhancedCostEvaluator;
import model.Problem;
import model.Solution;
import model.SolverEnvironment;

public class CollaboratingSolvers extends BaseSolver{
	List<SolverThread> sl;
	Problem problem;
	
	static final int TNum=2;
	static boolean useMathSolve=true;
	int sampleTime = 5000;
	int noImprove=0, noImproveLimit=3;
	
	class SolverThread extends Thread{
		BaseSolver solver;
		int solTime=-1;
		long oldIter=0;
		long iters=-1;
		int foundBest=0;
		
		public SolverThread(BaseSolver sl){
			solver = sl;
		}
		
		public void run(){
			try 
			{
				solTime = solver.solveWithTimeLimit(timeLimit);
			} catch (Exception e) {
//				System.err.println("Thread Exiting");
				e.printStackTrace();
				return;
			}
		}
		
	}
	
	int timeLimit;
	
	public CollaboratingSolvers(Problem problem,
			Solution initial_solution, Solution current_solution) {
		super(problem,initial_solution,current_solution);
		this.problem = problem;
		EnhancedCostEvaluator ece = new EnhancedCostEvaluator(new SolverEnvironment(problem, initial_solution));
		ece.computeCost();
		double difficulty = ((ece.getNewSolutionCost()-problem.getLowerBound())/(double)problem.getLowerBound());
		int size = 1000;//(int)Math.min(Math.max(1500/difficulty,1000),20000);
//		System.err.println("size="+size);
//		System.err.println("Problem difficulty = "+difficulty);
		best_solution = initial_solution.copy();
		best_solution.setCost(ece.getNewSolutionCost());
//		Random r = new Random(Problem.RANDOM_SEED);
		sl = new ArrayList<CollaboratingSolvers.SolverThread>();
		for(int i=0;i<TNum;i++){
//			if(i<TNum){	
				LateAcceptanceHeuristicSolver l1 = new LateAcceptanceHeuristicSolver(problem, initial_solution.copy(), current_solution.copy());
				l1.setLfa(size+100*size*i);
//				l1.setLfa(1000);
				Problem.RANDOM_SEED += 1940L;
				SolverThread t = new SolverThread(l1);
				sl.add(t);
//			}
//			else{
//				FlexDeluge l1 = new FlexDeluge(problem, initial_solution.copy());
//				Problem.RANDOM_SEED += 1940L;
//				SolverThread t = new SolverThread(l1);
//				sl.add(t);
//			}
		}
		
		if(useMathSolve){
			final boolean saveLP = false;
			final boolean use_grb = false;
			final boolean visualize = false;
			final int number_of_neighborhoods = problem.N/2; // if number_of_neighborhoods
			// is -1 then problem.N/2
			final int number_of_eligible_machines = 30;
			final int number_of_processes = 50;
			final int limitXVars = 500; // if limitXVars is -1 then no limit to
			// variables is applied
			final boolean include_dependency_constraints = true;
			final int time_limit_single_problem = 5;

			ProblemDecompositionMathSolver2 ipsolver = 
					new ProblemDecompositionMathSolver2(
							new SolverEnvironment(problem, initial_solution.copy(), current_solution.copy()),
							use_grb, visualize);
			ipsolver.setIncludeDependencyConstraints(include_dependency_constraints);
			ipsolver.set_nop_noem_non(number_of_processes,
					number_of_eligible_machines, number_of_neighborhoods);
			ipsolver.setSaveLP(saveLP);
			ipsolver.setLimitXVars(limitXVars);
			ipsolver.setTime_limit_single_problem(time_limit_single_problem);
			SolverThread t1 = new SolverThread(ipsolver);
			sl.add(t1);	
		}
	}

	public CollaboratingSolvers(Problem problem,
			Solution initial_solution) {
		this(problem,initial_solution,initial_solution.copy());
	}
	
	public int solveWithTimeLimit(int seconds){
		timeLimit = seconds;
		Chronograph c = new Chronograph();
		c.reset();
		Problem.VERBOSE = false;
		Thread.currentThread().setPriority(Thread.NORM_PRIORITY+1);
		for (SolverThread t : sl){
			if(!(t.solver instanceof ProblemDecompositionMathSolver2))
				t.start();
		}
		try{			
			while(true){
				Thread.sleep(sampleTime);
				int i=0;
//				System.out.println("Global best solution = "+best_solution.getCost());
				for (SolverThread t : sl){
					long lbc = t.solver.getBestCost();
					long cur_iter = t.solver.iterations;
					long newIters=100*(((cur_iter-t.oldIter)/(sampleTime/10)));
//					System.out.println("lbc"+i+++"="+lbc+" ("+newIters+ ")iter/sec");
//					if(((Math.abs(newIters-t.iters))/(double)t.iters)>.5) //we have a 50% change
//					{
//						double mult =1 - (lbc - problem.getLowerBound())/(double)problem.getLowerBound();
//						int newLfa = (int)((newIters)*mult);
//						if(((LateAcceptanceHeuristicSolver)t.solver).minlfa<newLfa)
//							newLfa = 3 *((LateAcceptanceHeuristicSolver)t.solver).minlfa;
//						((LateAcceptanceHeuristicSolver)t.solver).setLfa(newLfa);
//						System.err.println("Reseting lfa to "+newLfa);
//					}
					t.oldIter = cur_iter;
					t.iters = newIters;
					if(lbc<best_solution.getCost()){
						if(1.000001*lbc<best_solution.getCost())
							noImprove=0;
						best_solution = t.solver.getBestSolution();
						t.foundBest++;
						if(useMathSolve && 
								best_solution.getCost()<1.02*problem.getLowerBound() &&
							!sl.get(sl.size()-1).isAlive()){
							log.info("Starting MathSolve due to proximity to LB");
							env.setCurrentSolution(best_solution.copy());
							saveSolution(best_solution.getCost(), c.getElapsedTimeInSeconds(), false);
							sl.get(sl.size()-1).solver.setCurrentSolution(best_solution.copy());
							sl.get(sl.size()-1).start();
						}
												
					}
				}
				noImprove++;
				
				if(useMathSolve && noImprove>noImproveLimit && 
						!sl.get(sl.size()-1).isAlive()){
					log.info("Starting MathSolve due to no improvement");
					env.setCurrentSolution(best_solution.copy());
					saveSolution(best_solution.getCost(), c.getElapsedTimeInSeconds(), false);
					sl.get(sl.size()-1).solver.setCurrentSolution(best_solution.copy());
					sl.get(sl.size()-1).start();
				}
						
				log.debug("New Global best solution = "+best_solution.getCost()+" noImprove="+noImprove);
				
				i=0;
				for (SolverThread t1 : sl){
					long lbc = t1.solver.getBestCost();
					if((useMathSolve && (i==sl.size()-1 && lbc>best_solution.getCost())) //Allow 0% for math solvers
					 || (lbc>1.005*best_solution.getCost()) //Allow 0.1% for other
					 || t1.solver.noBestImprove>300000 && lbc>best_solution.getCost()
					 ){
//						t1.solver.best_cost = bestSolution.getCost();		
						t1.solver.setBestSolution(best_solution.copy());
					}
					i++;
				}
				c.lap();
				if (c.getElapsedTimeInSeconds() >= seconds-sampleTime/1000){
					env.setCurrentSolution(best_solution.copy());
					saveSolution(best_solution.getCost(), c.getElapsedTimeInSeconds(), false);
					sampleTime=100;
				}
				if (c.getElapsedTimeInSeconds() >= seconds-1){
					for (SolverThread t : sl){
						t.stop();
//						System.out.println(t.solver.toString()+" found best = "+t.foundBest);
					}
					break;
				}
			}
		}catch(InterruptedException ex){
		}finally{
			for (SolverThread t : sl){
				long lbc = t.solver.getBestCost();
				if(lbc<best_solution.getCost()){
					best_solution = t.solver.getBestSolution();
				}
			}
		}
		env.setCurrentSolution(best_solution.copy());
		return c.getElapsedTimeInSeconds();
	}
	
	public Solution getNewSolution(){
		return best_solution.copy();
	}
	
	public Solution getSolution(){
		return best_solution.copy();
	}
	
	public String getName(){
		return "Collaborating_"+(TNum)+"_LAHCSolver_"+(useMathSolve?1:0)+"_ProblemDecompositionSolver";
	}
	
	void transistAtoB(){
		
	}

}
